using Pdf2epub.ExtractStrategy;
using System;
using System.ComponentModel;
using System.Linq;
using System.Xml;

namespace Pdf2epub.Model
{
    public class HtmlReplacement : BaseObject
    {
        private static HtmlReplacementTag[] s_AllHeaderTags;

        private static HtmlReplacement[] s_PossibleReplacements;
        private bool m_RunRuleAsteriskAsListElement;
        private bool m_RunRulePreformat;
        private bool m_RunRuleToUppercase;
        private string m_StyleContent;
        private string m_StyleName;
        private HtmlReplacementTag m_Tag;

        public HtmlReplacement()
        {
        }

        protected HtmlReplacement(HtmlReplacementTag tag, string styleName, string styleContent, bool runRulePreformat, bool runRuleAsteriskAsListElement)
        {
            Tag = tag;
            StyleName = styleName;
            StyleContent = styleContent;
            RunRulePreformat = runRulePreformat;
            RunRuleAsteriskAsListElement = runRuleAsteriskAsListElement;
        }

        public static HtmlReplacementTag[] AllHeaderTags
        {
            get { return s_AllHeaderTags ?? (s_AllHeaderTags = GetAllHeaderTags()); }
        }

        public static HtmlReplacementTag[] AllParagraphTags
        {
            get { return s_AllHeaderTags ?? (s_AllHeaderTags = GetAllParagraphTags()); }
        }

        public static HtmlReplacement[] DefaultReplacements
        {
            get { return s_PossibleReplacements ?? (s_PossibleReplacements = CreatePossibleReplacements()); }
        }

        [DisplayName("'*' as first character indicated list element")]
        public bool RunRuleAsteriskAsListElement
        {
            get { return m_RunRuleAsteriskAsListElement; }
            set
            {
                m_RunRuleAsteriskAsListElement = value;
                OnPropertyChanged("RunRuleAsteriskAsListElement");
            }
        }

        [DisplayName("Format consecutive line as pre-formated")]
        public bool RunRulePreformat
        {
            get { return m_RunRulePreformat; }
            set
            {
                m_RunRulePreformat = value;
                OnPropertyChanged("RunRulePreformat");
            }
        }

        [DisplayName("Force uppercase")]
        public bool RunRuleToUppercase
        {
            get { return m_RunRuleToUppercase; }
            set
            {
                m_RunRuleToUppercase = value;
                OnPropertyChanged("RunRuleToUppercase");
            }
        }

        [DisplayName("Style definition")]
        public string StyleContent
        {
            get { return m_StyleContent; }
            set { m_StyleContent = value; OnPropertyChanged("StyleContent"); }
        }

        [DisplayName("Style Name")]
        public string StyleName
        {
            get { return m_StyleName; }
            set
            {
                m_StyleName = value; OnPropertyChanged("StyleName");
            }
        }

        [DisplayName("Default HTML Tag")]
        public HtmlReplacementTag Tag
        {
            get { return m_Tag; }
            set
            {
                m_Tag = value;
                OnPropertyChanged("Tag");
            }
        }

        public override bool Equals(object obj)
        {
            HtmlReplacement otherReplacement = obj as HtmlReplacement;
            if (otherReplacement == null)
                return false;
            return ToString().Equals(otherReplacement.ToString());
        }

        public XmlNode ExecuteRules(ContentBlock contentBlock, XmlNode renderNode)
        {
            if (RunRuleToUppercase)
            {
                renderNode.InnerText = renderNode.InnerText.ToUpperInvariant();
            }

            if (RunRulePreformat)
            {
                renderNode = RunPreformatRuleForParagraph(contentBlock, renderNode);
            }

            if (RunRuleAsteriskAsListElement)
            {
                renderNode = RunAsteriskAsListElement(contentBlock, renderNode);
            }

            if ((contentBlock.HtmlTag == HtmlReplacementTag.Li) || ((renderNode != null) && (renderNode.Name.Equals("li", StringComparison.InvariantCultureIgnoreCase))))
            {
                if (renderNode.ParentNode != null && !renderNode.ParentNode.Name.Equals("ul", StringComparison.InvariantCultureIgnoreCase))
                {
                    XmlElement lastNode = null;
                    if (renderNode.ParentNode.ChildNodes.Count > 1)
                    {
                        lastNode = renderNode.ParentNode.ChildNodes[renderNode.ParentNode.ChildNodes.Count - 2] as XmlElement;
                    }

                    XmlElement ulNode = null;

                    if ((lastNode != null) && (lastNode.Name.Equals("ul", StringComparison.InvariantCultureIgnoreCase)))
                    {
                        ulNode = lastNode;
                        renderNode.ParentNode.RemoveChild(renderNode);
                    }
                    else
                    {
                        ulNode = renderNode.OwnerDocument.CreateElement("ul");
                        renderNode.ParentNode.ReplaceChild(ulNode, renderNode);
                    }

                    ulNode.AppendChild(renderNode);
                }
            }
            return renderNode;
        }

        public override string ToString()
        {
            if (string.IsNullOrEmpty(StyleName))
                return "unknown style";
            return StyleName;
        }

        private static XmlElement ChangeNodeType(XmlNode renderNode, string targetType)
        {
            if (renderNode.OwnerDocument != null)
            {
                XmlElement preNode = renderNode.OwnerDocument.CreateElement(targetType);

                for (int i = 0; i < renderNode.ChildNodes.Count; i++)
                {
                    XmlNode child = renderNode.ChildNodes[i];
                    preNode.AppendChild(child.CloneNode(true));
                }

                if (renderNode.ParentNode != null)
                {
                    renderNode.ParentNode.ReplaceChild(preNode, renderNode);
                    return preNode;
                }
            }
            return null;
        }

        private static HtmlReplacement[] CreatePossibleReplacements()
        {
            return new[] {
                           new HtmlReplacement(HtmlReplacementTag.None, ".none",
                                               "{font-family:\"Times New Roman\", Times, serif}", false, false),
                           new HtmlReplacement(HtmlReplacementTag.P, ".parapgraph",
                                               "{font-family:\"Times New Roman\", Times, serif}", false, true),
                           new HtmlReplacement(HtmlReplacementTag.P, ".monospace",
                                               "{font-family: \"Courier New\", Courier, monospace; white-space: pre}", true, false),
                           new HtmlReplacement(HtmlReplacementTag.P, ".boldParagraph",
                                               "{font-weight:bold; font-family:\"Times New Roman\", Times, serif}", false, true),
                           new HtmlReplacement(HtmlReplacementTag.P, ".italicParagraph",
                                               "{font-style:italic; font-family:\"Times New Roman\", Times, serif}", false, true),
                           new HtmlReplacement(HtmlReplacementTag.P, ".italicBoldParagraph",
                                               "{font-style:italic; font-weight:bold; font-family:\"Times New Roman\", Times, serif}", false, true),
                           new HtmlReplacement(HtmlReplacementTag.Li, ".listElement",
                                               "{font-family:\"Times New Roman\", Times, serif}", false, false),
                           new HtmlReplacement(HtmlReplacementTag.H1, ".header1",
                                               "{font-family:Arial, Helvetica, sans-serif}", false, false),
                           new HtmlReplacement(HtmlReplacementTag.H2, ".header2",
                                               "{font-family:Arial, Helvetica, sans-serif}", false, false),
                           new HtmlReplacement(HtmlReplacementTag.H3, ".header3",
                                               "{font-family:Arial, Helvetica, sans-serif}", false, false),
                           new HtmlReplacement(HtmlReplacementTag.H4, ".header4",
                                               "{font-family:Arial, Helvetica, sans-serif}", false, false),
                           new HtmlReplacement(HtmlReplacementTag.H5, ".header5",
                                               "{font-family:Arial, Helvetica, sans-serif}", false, false),
                           new HtmlReplacement(HtmlReplacementTag.H6, ".header6",
                                               "{font-family:Arial, Helvetica, sans-serif}", false, false),
                           new HtmlReplacement(HtmlReplacementTag.H6, ".header6",
                                               "{font-family:Arial, Helvetica, sans-serif}", false, false),
                           new HtmlReplacement(HtmlReplacementTag.Remove, "Remove", string.Empty, false, false)
                       };
        }

        private static bool DoesBlockContainMoreThanOneLine(ContentBlock contentBlock)
        {
            if ((contentBlock == null) || (string.IsNullOrEmpty(contentBlock.Content)))
                return false;
            return contentBlock.Content.Trim().Count(x => x == '\n') > 0;
        }

        private static HtmlReplacementTag[] GetAllHeaderTags()
        {
            return new[]
                       {
                           HtmlReplacementTag.H1,
                           HtmlReplacementTag.H2,
                           HtmlReplacementTag.H3,
                           HtmlReplacementTag.H4,
                           HtmlReplacementTag.H5,
                           HtmlReplacementTag.H6
                       };
        }

        private static HtmlReplacementTag[] GetAllParagraphTags()
        {
            return new[]
                       {
                           HtmlReplacementTag.P,
                           HtmlReplacementTag.H1,
                           HtmlReplacementTag.H2,
                           HtmlReplacementTag.H3,
                           HtmlReplacementTag.H4,
                           HtmlReplacementTag.H5,
                           HtmlReplacementTag.H6,
                           HtmlReplacementTag.Li,
                           HtmlReplacementTag.Remove,
                       };
        }

        private static XmlElement RunPreformatRuleForParagraph(ContentBlock contentBlock, XmlNode renderNode)
        {
            if (DoesBlockContainMoreThanOneLine(contentBlock))
            {
                if (renderNode.Name.Equals("pre", StringComparison.InvariantCultureIgnoreCase))
                    return null;

                return ChangeNodeType(renderNode, "pre");
            }
            return null;
        }

        private XmlElement RunAsteriskAsListElement(ContentBlock contentBlock, XmlNode renderNode)
        {
            if (contentBlock.ContentType == ContentType.Image)
                return null;

            if (!contentBlock.Content.Trim().StartsWith("*"))
                return null;

            XmlElement newNode = ChangeNodeType(renderNode, "li");
            if (newNode != null)
            {
                newNode.InnerText = newNode.InnerText.Trim().TrimStart('*').Trim();
            }
            return newNode;
        }
    }
}